package controllers

import (
	"context"
	"encoding/json"
	"gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
	"gitverse.ru/synapse/kubelatte/pkg/observability/logs"
	"gitverse.ru/synapse/kubelatte/pkg/operator"
	"gitverse.ru/synapse/kubelatte/pkg/util"
	"k8s.io/apimachinery/pkg/api/errors"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/event"
	"sigs.k8s.io/controller-runtime/pkg/predicate"
	"strings"
	"sync"
)

// TriggerReconciler reconciles a Template object
type TriggerReconciler struct {
	client.Client
	Scheme *runtime.Scheme
	once   sync.Once

	triggerObj        v1alpha1.Trigger
	deletedTriggerObj v1alpha1.Trigger

	initedFlag bool
}

func (r *TriggerReconciler) AddOrUpdateEventHndl(ctx context.Context, req ctrl.Request) (err error) {
	return nil
}

func (r *TriggerReconciler) DeleteEventHndl(req ctrl.Request) (err error) {
	return nil
}

func (r *TriggerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	operator.Mux.Lock()
	defer operator.Mux.Unlock()

	r.once.Do(func() {
		r.initedFlag = true
	})
	// Check delete event
	err := r.Get(ctx, req.NamespacedName, &r.triggerObj)
	if err != nil {
		if errors.IsNotFound(err) {
			if err = r.DeleteEventHndl(req); err != nil {
				return ctrl.Result{}, err
			}
		}
		return ctrl.Result{}, err
	}

	// Add or update event
	err = r.AddOrUpdateEventHndl(ctx, req)
	if err != nil {
		return ctrl.Result{}, err
	}

	return r.updateStatus(ctx, r.triggerObj, false)
}

func (r *TriggerReconciler) updateStatus(ctx context.Context, trigger v1alpha1.Trigger, isAudit bool) (ctrl.Result, error) {
	data, _ := json.Marshal(trigger.Spec)
	if operator.CheckHashInStatus(trigger.Status.Conditions) {
		for i, condition := range trigger.Status.Conditions {
			if condition.Type == v1alpha1.LastHandledHash {
				sha1Hash := operator.GetHash(data)
				if isAudit && strings.Compare(trigger.Status.Conditions[i].Message, sha1Hash) != 0 {
					logs.Debugf("Trigger status checker: version compare failed: %s != %s", trigger.Status.Conditions[i].Message, sha1Hash)
				}
				trigger.Status.Conditions[i].Message = sha1Hash
				trigger.Status.Conditions[i].LastTransitionTime = v1.Now()
			}
		}
	} else {
		trigger.Status.Conditions = append(trigger.Status.Conditions, operator.AddHashCondition(data))
	}
	err := r.Status().Update(ctx, &trigger, &client.SubResourceUpdateOptions{})
	if err != nil {
		logs.Error("Trigger controller: failed update status crd: " + err.Error())
		return ctrl.Result{}, err
	}
	return ctrl.Result{}, nil
}

func (r *TriggerReconciler) manageEvents() predicate.Predicate {
	return predicate.Funcs{
		CreateFunc: func(e event.CreateEvent) bool {
			operator.EventOperation = operator.AddEvent
			obj := *e.Object.(*v1alpha1.Trigger)
			if operator.IsOperatorInClusterMode() && !CheckNamespace(obj.Namespace, operator.NSSelectorCommon) {
				return false
			}
			util.SendEvent(operator.StorageUpdateTriggerEvent, obj)
			operator.PmTriggersTotal.Inc()
			return true
		},
		UpdateFunc: func(e event.UpdateEvent) bool {
			if e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() {
				operator.EventOperation = operator.UpdateEvent
				obj := *e.ObjectNew.(*v1alpha1.Trigger)
				if operator.IsOperatorInClusterMode() && !CheckNamespace(obj.Namespace, operator.NSSelectorCommon) {
					return false
				}
				util.SendEvent(operator.StorageUpdateTriggerEvent, obj)
				return true
			}
			return false
		},
		DeleteFunc: func(e event.DeleteEvent) bool {
			operator.EventOperation = operator.DeleteEvent
			r.deletedTriggerObj = *e.Object.(*v1alpha1.Trigger)
			if operator.IsOperatorInClusterMode() && !CheckNamespace(r.deletedTriggerObj.Namespace, operator.NSSelectorCommon) {
				return false
			}
			util.SendEvent(operator.StorageDeleteTriggerEvent, r.deletedTriggerObj)
			operator.PmTriggersTotal.Dec()
			return true
		},
	}
}

// SetupWithManager sets up the controller with the Manager.
func (r *TriggerReconciler) SetupWithManager(mgr ctrl.Manager) error {

	return ctrl.NewControllerManagedBy(mgr).
		For(&v1alpha1.Trigger{}).
		WithEventFilter(r.manageEvents()).
		Complete(r)
}

func (r *TriggerReconciler) HealthCheck() bool {
	return r.initedFlag
}
